
#include "zen_game_label.h"
#include "zen_game_activity.h"
#include "zen_utf8.h"
#include "zen_ticker.h"

namespace Zen
{
	namespace Game
	{

		/// label
		void LabelI::SetText(std::string const& text)
		{
			if (mText == text) return;
			mText = text;
			mIsTextDirty = true;
		}

		std::string const& LabelI::GetText() const
		{
			return mText;
		}

		TextLabel::TextLabel() {}

		void TextLabel::Flush()
		{
			if (mText.empty() || mRenderer == nullptr)
			{
				this->SetSize({});
				return;
			}
			bool dirty = false;
			if (mIsTextDirty)
			{
				mUnicodeText = Zen::ToU32(mText);
				mIsTextDirty = false;
				dirty = true;
			}

			if (dirty || mIsStyleDirty || mIsRenderDirty)
			{
				mIsRenderDirty = false;
				mIsStyleDirty = false;
				auto texture = mRenderer->RenderTexture(mUnicodeText, mStyle);
				if (!texture)
					this->SetTextureFrame(nullptr);
				else
					this->SetTexture(texture);
				dirty = true;
			}

			if (mIsFontSizeDirty)
			{
				mIsFontSizeDirty = false;
				dirty = true;
			}

			if (dirty && mTextureFrame)
			{
				float scale = mFontSize * mRenderer->GetScale();
				this->SetSize({ mTextureFrame->GetTexture()->GetWidth() * scale, mTextureFrame->GetTexture()->GetHeight() * scale });
			}
			Sprite::Flush();
		}

		void TextLabel::OnReady(std::shared_ptr<RenderStack> stack, float interval)
		{
			if (mText.empty() || mRenderer == nullptr) return;
			this->Flush();
			Sprite::OnReady(stack, interval);
		}

		void TextLabel::OnDraw()
		{
			if (mText.empty() || mRenderer == nullptr) return;
			Sprite::OnDraw();
		}

		std::shared_ptr<Zen::Graphics::Texture> DefaultTextRender::RenderTexture(const std::u32string& text, const Zen::FontStyle& style)
		{
			if (!mFontBrush) return nullptr;
			auto e = Zen::Game::Activity::Only()->GetGraphicsEngine();
			if (!e) return nullptr;

			auto render = FontRender::Generate(mFontBrush, text, style);

			if (render->Width() && render->Height())
			{
				auto image = render->RenderImage(0, 0, { 0, 0 });

				auto buffer = image->Data();
				if (image && image->Size())
				{
					return e->CreateTexture(image->PixelType(), image->Width(), image->Height(), image->Data());
				}
			}

			return nullptr;
		}

		void TextLabel::SetTextRender(std::shared_ptr<TextRender> render)
		{
			if (mRenderer == render) return;
			mRenderer = render;
			mIsRenderDirty = true;
		}
		std::shared_ptr<TextRender> TextLabel::GetTextRender()
		{
			return mRenderer;
		}
		void TextLabel::SetFontSize(float size)
		{
			if (size == mFontSize) return;
			mFontSize = size;
			mIsFontSizeDirty = true;
		}

		float TextLabel::GetFontSize() const
		{
			return mFontSize;
		}

		void TextLabel::SetFontStyle(Zen::FontStyle style)
		{
			mStyle = style;
			mIsStyleDirty = true;
		}

		FontStyle const& TextLabel::GetFontStyle()
		{
			return mStyle;
		}

	} // namespace Game
} // namespace Zen

/// Tilemap

namespace Zen
{
	namespace Game
	{
		std::shared_ptr<TileMap> TileMap::Generate()
		{
			return std::make_shared<TileMap>();
		}
		std::shared_ptr<TileMap> TileMap::Generate(char32_t start_code, size_t x_count, size_t y_count)
		{
			auto tilemap = std::make_shared<TileMap>();

			float width = 1.f / (float)x_count;
			float height = 1.f / (float)y_count;

			auto code = start_code;
			for (size_t y = 0; y < y_count; ++y)
			{
				float py = (float)y / (float)y_count;
				float pyb = py + height;
				float px = 0;
				for (size_t x = 0; x < x_count; ++x)
				{
					float pxr = px + width;
					TileInfo tile;
					tile.Unicode = code++;
					tile.Left = px;
					tile.Right = pxr;
					tile.Top = py;
					tile.Bottom = pyb;
					tilemap->AddCharactor(tile);
					px = pxr;
				}
			};
			return tilemap;
		}
		void TileMap::AddCharactor(TileInfo tc)
		{
			mCharMap[tc.Unicode] = tc;
			++mVersion;
		}
		TileInfo const* TileMap::GetCharactor(char32_t code) const
		{
			auto iter = mCharMap.find(code);
			if (iter == mCharMap.end()) return nullptr;
			return &(iter->second);
		}

		int TileMap::GetVersion() const
		{
			return mVersion;
		}

		TileLabel::TileLabel()
		{
			mDrawMode = Zen::Graphics::EDrawMode::Triangle;
		}

		void TileLabel::OnTextureChange()
		{
			mIsFrameDirty = true;
			mFrameVersion = (mTextureFrame ? mTextureFrame->GetVersion() : 0);
		}

		void TileLabel::Flush()
		{
			if (!mTileMap || !mTextureFrame || !mTextureFrame->GetTexture() || mText.empty())
			{
				mIsEmpty = true;
				return;
			}
			bool isVertexDirty{};
			bool isCoordDirty{};
			bool dirty{};
			if (mIsTextDirty)
			{
				mUnicodeText = ToU32(mText);
				mIsTextDirty = false;
				dirty = true;
			}
			if (mIsTileMapDirty || mTileMapVersion != mTileMap->GetVersion())
			{
				mTileMapVersion = mTileMap->GetVersion();
				mIsTileMapDirty = false;
				dirty = true;
			}
			if (mIsTileSizeDirty)
			{
				mIsTileSizeDirty = false;
				isVertexDirty = true;
			}
			if (mIsFrameDirty || mFrameVersion != mTextureFrame->GetVersion())
			{
				mIsFrameDirty = false;
				mFrameVersion = mTextureFrame->GetVersion();
				isCoordDirty = true;
			}
			size_t vertexCount = GetVertexCount();
			if (isVertexDirty || dirty)
			{
				vertexCount = UpdatePolygonVertex_();
			}
			if (isCoordDirty || dirty)
			{
				auto s = UpdateTextureCoord_();
				__zen_must(vertexCount == s);
			}
			if (vertexCount != GetVertexCount())
			{
				SetVertexCount(vertexCount);
				std::vector<uint16_t> index;
				for (size_t i = 0; i < vertexCount; i += 4)
				{
					index.push_back((uint16_t)i);
					index.push_back((uint16_t)i + 1);
					index.push_back((uint16_t)i + 2);
					index.push_back((uint16_t)i + 2);
					index.push_back((uint16_t)i + 1);
					index.push_back((uint16_t)i + 3);
				}
				auto indexbuffer = NewIndexBuffer();
				indexbuffer->Initialize(index.size(), index.data());
				SetIndexBuffer(indexbuffer);
			}
			mIsEmpty = (vertexCount == 0);
		}

		void TileLabel::OnReady(std::shared_ptr<RenderStack> stack, float interval)
		{
			this->Flush();
			if (mIsEmpty) return;
			FigureTexture::OnReady(stack, interval);
		}

		void TileLabel::OnDraw()
		{
			if (mIsEmpty) return;
			FigureTexture::OnDraw();
		}

		void TileLabel::SetTileMap(std::shared_ptr<TileMap> tileMap)
		{
			if (mTileMap == tileMap) return;
			mTileMap = tileMap;
			mTileMapVersion = mTileMap ? mTileMap->GetVersion() : 0;
			mIsTileMapDirty = true;
		}

		std::shared_ptr<TileMap> TileLabel::GetTileMap() const
		{
			return mTileMap;
		}

		void TileLabel::SetTileSize(Point2 tileSize)
		{
			if (mTileSize == tileSize) return;
			mTileSize = tileSize;
			mIsTileSizeDirty = true;
		}

		Point2 TileLabel::GetTileSize() const
		{
			return mTileSize;
		}

		size_t TileLabel::UpdatePolygonVertex_()
		{
			Point2 pos{};
			std::vector<Point2> vertex;
			float maxX = 0;
			for (auto c : mUnicodeText)
			{
				if (c == '\r' || c == '\n')
				{
					pos.y += 1.f;
					maxX = std::max(pos.x, maxX);
					pos.x = 0.f;
					continue;
				}
				auto tile = mTileMap->GetCharactor(c);
				if (tile)
				{
					auto x0 = pos.x + tile->Move.x;
					auto x1 = x0 + tile->Size.x;
					auto y1 = -pos.y + 1.f + tile->Move.y;
					auto y0 = y1 + tile->Size.y;
					vertex.push_back({ x0, y0 });
					vertex.push_back({ x0, y1 });
					vertex.push_back({ x1, y0 });
					vertex.push_back({ x1, y1 });
					pos.x += tile->Advance;
				}
				else
				{
					pos.x += 1.f;
				}
			}
			if (vertex.empty())
			{
				return 0;
			}

			maxX = std::max(pos.x, maxX);
			float maxY = pos.y + 1.f;
			for (auto& v : vertex)
			{
				v.x /= maxX;
				v.y = (v.y + maxY) / maxY;
			}
			SetSize(mTileSize * Point2{ maxX, maxY });

			auto vbuffer = NewBuffer();
			vbuffer->Initialize(sizeof(Point2) * vertex.size(), vertex.data());
			SetPolygonBuffer(vbuffer, 2);

			return vertex.size();
		}

		size_t TileLabel::UpdateTextureCoord_()
		{
			std::vector<Point2> coords;
			for (auto c : mUnicodeText)
			{
				if (c == '\r' || c == '\n')
				{
					continue;
				}
				auto tile = mTileMap->GetCharactor(c);
				if (tile)
				{
					coords.push_back({ tile->Left, tile->Top });
					coords.push_back({ tile->Left, tile->Bottom });
					coords.push_back({ tile->Right, tile->Top });
					coords.push_back({ tile->Right, tile->Bottom });
				}
			}

			auto textureCoords = mTextureFrame->ProjectCoords(std::move(coords));
			auto tbuffer = NewBuffer();
			tbuffer->Initialize(sizeof(Point2) * textureCoords.size(), textureCoords.data());
			SetTextureCoordBuffer(tbuffer);
			return textureCoords.size();
		}

	} // namespace Game
} // namespace Zen
